package org.mikrosopht.client;

import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONException;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowResizeListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusListenerAdapter;
import com.google.gwt.user.client.ui.Frame;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MouseListenerAdapter;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class MovieList extends Composite {
  private static final int MOVIES_PER_PAGE = 25;
  private static final String VOTER_PATH_URL = "/vote/voters";
  private static final String METACRITIC_IMG = MovieVoteConstants.IMAGE_PATH_URL + "/metacritic.png";
  private static final String ROTTEN_IMG = MovieVoteConstants.IMAGE_PATH_URL + "/rotten.png";
  private static final String TRAILER_IMG = MovieVoteConstants.IMAGE_PATH_URL + "/youtube.png";

  private VerticalPanel mainPanel = new VerticalPanel();
  private HorizontalPanel searchPanel = new HorizontalPanel();
  private HorizontalPanel movieListPanel = new HorizontalPanel();
  public Frame rightFrame = new Frame();
  private Grid movieGrid = new Grid(25, 4);
  private ResizeListener resizeListener = new ResizeListener();
  private static final String[] columns = new String[] {"Name", "Reviews", "Tally", "Vote"};
  private String currentSearch = "";
  Request inFlightRequest = null;

  private Runnable updateMovieList = new Runnable() {
    public void run() {
      populateMovieList(false);
    }
  };
  private Runnable updateMovieListResetStart = new Runnable() {
    public void run() {
      populateMovieList(true);
    }
  };
  private MovieListPagerController pagerController = new MovieListPagerController(updateMovieList);
  private MovieListSorterController sorterController = new MovieListSorterController(updateMovieListResetStart);
  private TextBox searchBox = new TextBox();

  public MovieList() {
    searchBox.addKeyboardListener(new SearchBoxChangeHandler());
    searchPanel.add(new Label("Search:"));
    searchPanel.add(searchBox);
    searchPanel.add(sorterController);
    movieListPanel.add(movieGrid);
    movieListPanel.add(rightFrame);
    mainPanel.add(searchPanel);
    mainPanel.add(pagerController);
    mainPanel.add(movieListPanel);
    movieGrid.setStyleName("movie_list");
    movieGrid.getRowFormatter().setStyleName(0, "header");
    for (int i = 0; i < columns.length; ++i) {
      movieGrid.setText(0, i, columns[i]);
    }
    initWidget(mainPanel);
    Window.addWindowResizeListener(resizeListener);
  }

  public void setSorting(String sort1, String sort2) {
    if (sort2.equals("name")) {
      if (sort1.equals("-tally")) {
        sorterController.setChecked(MovieListSorter.Button.TALLY);
      } else if (sort1.equals("-date_added")) {
        sorterController.setChecked(MovieListSorter.Button.NEWEST);
      } else if (sort1.equals("-review_average_score")) {
        sorterController.setChecked(MovieListSorter.Button.REVIEW);
      }
    }
  }

  public void populateMovieList(boolean resetStart) {
    int startAt = 0;
    int moviesPerPage = MOVIES_PER_PAGE;
    if (pagerController.showAll) {
      moviesPerPage = 0;
    } else {
      startAt = pagerController.offset * moviesPerPage;
    }
    currentSearch = searchBox.getText();
    if (resetStart) {
      startAt = 0;
      pagerController.resetOffset();
    }

    String sort1 = "-tally";
    String sort2 = "name";
    MovieListSorter.Button sorting = sorterController.getChecked();
    switch (sorting) {
      case TALLY:
        sort1 = "-tally";
        break;
      case NEWEST:
        sort1 = "-date_added";
        break;
      case REVIEW:
        sort1 = "-review_average_score";
        break;
    }

    String url = URL.encode(MovieVoteConstants.MOVIE_LIST_JSON_URL + "?count=" + moviesPerPage + "&start=" + startAt +
                            "&namelike=" + currentSearch + "&sort1=" + sort1 + "&sort2=" + sort2);
    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
    try {
      inFlightRequest = builder.sendRequest(null, new PopulateMovieListHandler());
    } catch (RequestException e) {
      Window.alert("Couldn't retrieve JSON: " + e);
    }
  }

  private class SearchBoxChangeHandler extends KeyboardListenerAdapter {
    public void onKeyUp(Widget sender, char keyCode, int modifiers) {
      String newSearch = ((TextBox) sender).getText();
      if (!newSearch.equals(currentSearch)) {
        populateMovieList(true);
      }
    }
  }

  private class PopulateMovieListHandler implements RequestCallback {
    public void onError(Request request, Throwable exception) {
      Window.alert("Couldn't retrieve JSON: " + exception);
    }
    public void onResponseReceived(Request request, Response response) {
      if (request != inFlightRequest) {
        return;
      }
      if (response.getStatusCode() == 200) {
        try {
          JSONObject object = JSONParser.parse(response.getText()).isObject();
          int maxOffset = Integer.parseInt(object.get("count").isString().stringValue()) / MOVIES_PER_PAGE;
          pagerController.setMaxOffset(maxOffset);
          updateMovieListFromJSON(object.get("movies").isArray());
          DeferredCommand.addCommand(new Command() {
            public void execute() {
              resizeListener.onWindowResized(Window.getClientWidth(), Window.getClientHeight());
            }
          });
        } catch (JSONException e) {
          MovieVote.redirectToLogin();
        }
      } else {
        Window.alert("Couldn't retrieve JSON: " + response.getStatusText());
      } 
      pagerController.updateButtons();
    }
  }

  private void updateMovieListFromJSON(JSONArray array) {
    movieGrid.resizeRows(array.size() + 1);
    for (int i = 0; i < array.size(); i++) {
      JSONObject object = array.get(i).isObject();
      updateMovieListRowFromJSON(i + 1, object);
    }
  }

  private void updateMovieListRowFromJSON(int row, JSONObject object) {
    String movieName = object.get("name").isString().stringValue();
    String imdbUrl = object.get("imdb_url").isString().stringValue();
    String rottenUrl = object.get("rotten_url").isString().stringValue();
    String metacriticUrl = object.get("metacritic_url").isString().stringValue();
    String trailerUrl = object.get("youtube_trailer_url").isString().stringValue();
    String reviewScore = object.get("review_average_score").isString().stringValue();
    reviewScore = isFloat(reviewScore) ? reviewScore : "";
    String tally = object.get("tally").isString().stringValue();
    String tallyDetail = object.get("tally_detail").isString().stringValue();

    // Name
    movieGrid.setWidget(row, 0, createMovieNameWidget(movieName, imdbUrl));
    movieGrid.getCellFormatter().setStyleName(row, 0, "movie");

    // Reviews
    movieGrid.setWidget(row, 1, createReviewWidget(trailerUrl, rottenUrl, metacriticUrl,
                                                     reviewScore));
    movieGrid.getCellFormatter().setStyleName(row, 1, "movie");

    // Tally
    int minusVotes = Integer.parseInt(object.get("downvoter_count").isString().stringValue());
    int plusVotes = Integer.parseInt(object.get("voter_count").isString().stringValue());
    int screenings = Integer.parseInt(object.get("screenings").isString().stringValue());
    TallyWidget tallyWidget = new TallyWidget(movieName);
    tallyWidget.update(tally, tallyDetail, plusVotes, minusVotes, screenings);
    movieGrid.setWidget(row, 2, tallyWidget);
    movieGrid.getCellFormatter().setStyleName(row, 2, "movie");
    if (screenings > 0) {
      movieGrid.getRowFormatter().setStyleName(row, "screened");
    } else {
      movieGrid.getRowFormatter().removeStyleName(row, "screened");
    }

    // Vote operations
    int plusVoted = Integer.parseInt(object.get("voted").isString().stringValue());
    int minusVoted = Integer.parseInt(object.get("downvoted").isString().stringValue());

    // Don't update movie list when unneeded for sort
    Runnable runAfterVote = null;
    if (sorterController.getChecked() == MovieListSorter.Button.TALLY) {
      runAfterVote = updateMovieList;
    }
    movieGrid.setWidget(row, 3, new VoteWidget(runAfterVote, movieName, tallyWidget,
                                               plusVoted > 0, minusVoted > 0));
    movieGrid.getCellFormatter().setStyleName(row, 3, "movie");
  }

  private Widget createMovieNameWidget(String movieName, String imdbUrl) {
    if (imdbUrl.length() > 0) {
      HTML widget = new HTML("<a href=\"" + imdbUrl + "\">" + movieName + "</a>");
      widget.addMouseListener(new RightFrameUpdater(imdbUrl));
      return widget;
    } else {
      return(new Label(movieName));
    }
  }

  private Widget createReviewWidget(String trailerUrl, String rottenUrl, String metacriticUrl,
                                    String reviewScore) {
    Grid reviewGrid = new Grid(1, 4);
    HTML link;
    if (trailerUrl.length() > 0) {
      link = new HTML("<a href=\"" + trailerUrl + "\"><img src=\"" + TRAILER_IMG + "\"></a>");
      link.addMouseListener(new RightFrameUpdater(trailerUrl));
      reviewGrid.setWidget(0, 0, link);
    }
    if (rottenUrl.length() > 0) {
      link = new HTML("<a href=\"" + rottenUrl + "\"><img src=\"" + ROTTEN_IMG + "\"></a>");
      link.addMouseListener(new RightFrameUpdater(rottenUrl));
      reviewGrid.setWidget(0, 1, link);
    }
    if (metacriticUrl.length() > 0) {
      link = new HTML("<a href=\"" + metacriticUrl + "\"><img src=\"" + METACRITIC_IMG + "\"></a>");
      link.addMouseListener(new RightFrameUpdater(metacriticUrl));
      reviewGrid.setWidget(0, 2, link);
    }
    reviewGrid.setWidget(0, 3, new Label(reviewScore));
    return reviewGrid;
  }
 
  private boolean isFloat(String str) {
    try {
      Float.parseFloat(str);
    } catch (NumberFormatException nfe) {
      return false;
    }
    return true;
  }

  private class ResizeListener implements WindowResizeListener {
    public void onWindowResized(int width, int height) {
      int frameHeight = Math.max(height - rightFrame.getAbsoluteTop(), movieGrid.getOffsetHeight());
      if (frameHeight < 1) {
        frameHeight = 1;
      }
      int frameWidth = width - movieGrid.getOffsetWidth() - 100;
      if (frameWidth < 1) {
        frameWidth = 1;
      }
      rightFrame.setSize("" + frameWidth, "" + frameHeight); 
    }
  }

  private class RightFrameUpdater extends MouseListenerAdapter {
    private String url;
    private UpdateTimer timer;

    private class UpdateTimer extends Timer {
      public void run() {
        rightFrame.setUrl(url);
      }
    }
    public RightFrameUpdater(String url) {
      this.url = url;
      timer = new UpdateTimer();
    }

    public void onMouseEnter(Widget sender) {
      timer.schedule(1000);
    }

    public void onMouseLeave(Widget sender) {
      timer.cancel();
    }
  }
}
